using System;
using System.Collections;
using System.Collections.Generic;
using System.Html;
using System.Runtime.CompilerServices;
using jQueryApi;

namespace Awaiter
{
    public delegate bool AwaitBoolDelegate();

    public delegate bool AwaitBoolDelegateObject(object arg1);

    public delegate object AwaitObjectDelegate();

    public delegate void AwaitDelegate(Await aw);

    [IgnoreNamespace]
    public sealed class WaitTarget
    {
        public AwaitBoolDelegate Fn;
        public int TargetMs;
    }

    [IgnoreNamespace]
    public partial class Await
    {
        public static bool LogActions;
        public static bool PassExceptions;

        private static readonly Dictionary<string, bool> Scripts = new Dictionary<string, bool>();
        private static readonly int SimulatedLatency = (new string[] {"192.168.1.19", "localhost"}).Contains(Window.Location.Hostname) ? 600 : 0;
        public static Dictionary<string, object> Files = new Dictionary<string, object>();
        public readonly AwaitTimers AwaitTimer = new AwaitTimers();
        public readonly Dictionary<string, object> Vars = new Dictionary<string, object>();
        public readonly Dictionary<string, bool> VarsModified = new Dictionary<string, bool>();
        private readonly List<string> _traceList = new List<string>();
        public Dictionary<string, jQueryEventHandler> Events;
        private bool _abort;
        private AwaitDelegate _alwaysAction;
        private Await _awp;
        private int _count;
        private Dictionary<string, AwaitDelegate> _exceptionHandlers;
        private bool _ignore;
        private int _maxWaitStyleSheet = 20000;
        private List<AwaitDelegate> _parallelActions;
        private int _queCount;
        private List<AwaitDelegate> _serialActions;
        private List<AwaitDelegate> _serialSaved;
        private Exception _thrown;

        public object this[string n]
        {
            get { return Vars[n.Substr(0, 1) == "@" ? n.Substr(1) : n]; }
            set
            {
                n = n.Substr(0, 1) == "@" ? n.Substr(1) : n;
                Vars[n] = value;
                VarsModified[n] = true;
            }
        }

        public object Result
        {
            get { return this["result"]; }
            set { this["result"] = value; }
        }

        public Await Awp
        {
            get { return _awp; }
        }

        public static Await AsyncAw
        {
            get { return new Await(); }
        }

        public static void Trigger(string nm, object arg)
        {
            object[] args = new object[1];
            args[0] = arg;
            jQuery.Document.Trigger(nm, args);
        }

        public static Dictionary Vita(string key, string record)
        {
            return (Dictionary) ((Dictionary) Files[key])[record];
        }

        private void DoAlways(Action<Await> fn)
        {
            Await my = this;
            if (_alwaysAction == null)
            {
                fn(my);
                return;
            }
            AwaitTimer.SetTimeout(
                delegate
                {
                    _alwaysAction(my);
                    fn(my);
                }, 1);
        }

        public void Handle(Exception ex)
        {
            string exType = (Script.IsValue(ex.Message) ? ex.Message : "none").Split(":")[0].Trim();
            if (_exceptionHandlers != null && _exceptionHandlers.ContainsKey(exType))
            {
                Trace(String.Format("Handling await exception: {0}", ex));
                AwaitDelegate fn = _exceptionHandlers[exType];
                _thrown = ex;
                fn(this);
                return;
            }
            // Handle default exceptions
            switch (exType)
            {
                default:
                    if (_awp != null)
                    {
                        ProxyVars();
                        _awp.Handle(ex);
                    }
                    else
                    {
                        Error(String.Format("Aborting await chain with exception: \"{0}\"", ex));
                        string[] trace = (string[]) Script.Literal("printStackTrace({0})", new Dictionary("e", ex, "guess", true));
                        foreach (string line in trace)
                        {
                            Log(line);
                        }
                        Abort();
                        throw ex;
                    }
                    break;
            }
        }

        public static void AbortAw(Await aw)
        {
            aw.Abort();
        }

        public static void IgnoreAw(Await aw)
        {
            aw.Done();
        }

        public static void FinishAw(Await aw)
        {
            aw.Finish();
        }

        public static void RethrowAw(Await aw)
        {
            aw._ignore = true;
            aw.DoAlways(
                delegate
                {
                    if (aw.Awp != null && aw._thrown != null)
                    {
                        aw.ProxyVars();
                        aw.Awp.Handle(aw._thrown);
                    }
                });
        }

        private Await DoAbort()
        {
            _ignore = true;
            DoAlways(
                delegate
                {
                    if (_awp != null)
                    {
                        ProxyVars();
                        _awp.Abort();
                    }
                });
            return this;
        }

        private void ActionClosure(AwaitDelegate f)
        {
            Await my = this;
            AwaitTimer.SetTimeout(delegate { f(my); }, 1);
        }

        /*
        public void Trace(string msg)
        {
            Trace("Started: " + msg);
            _traceList.Add(msg);
        }

        public void Test()
        {
            AwaitClassTests tests = new AwaitClassTests();
            tests.TestMethod1();
        }
        */

        [AlternateSignature]
        private extern void LogDoFn(object fn);

        // ReSharper disable UnusedMember.Local
        [AlternateSignature]
        private extern void LogDoFn(object fn, object arg1);

        // ReSharper restore UnusedMember.Local

        [AlternateSignature]
        private extern void LogDoFn(object fn, object arg1, object arg2);

        private void LogDoFn(object fn, object arg1, object arg2, object arg3)
        {
            if (!LogActions)
                return;
            object[] t = (object[]) Script.Literal("{0}._targets", fn);
            if (Script.IsValue(t))
            {
                string args = "()";
                if (Script.IsValue(arg1))
                    args = String.Format("({0},{1},{2})", arg1, arg2, arg3);

                if (t.Length > 1 && Script.IsValue(t[1]))
                {
                    string a = (string) Script.Literal("{0}._targets[1].name", fn);
                    if (Script.IsValue(a) && !String.IsNullOrEmpty(a))
                        Log(String.Format("Await doing {0}{1}", a, args));
                    else
                    {
                        a = Script.Literal("{0}._targets[0]", fn).GetType().Name;
                        Log(String.Format("Await doing (anonymous delegate){0}{1}", a, args));
                    }
                }
            }
        }

        public static Dictionary FileDictionary(string key)
        {
            return (Dictionary) Files[key];
        }

        private void ProxyVars()
        {
            if (_awp != null)
                foreach (KeyValuePair<string, object> p in Vars)
                {
                    if (!_awp.VarsModified[p.Key])
                        _awp.Vars[p.Key] = p.Value;
                }
        }

        private void LoadScriptWait(string url)
        {
            //Log(String.Format("LoadScriptWait {0}", url));
            if (!Scripts[url])
            {
                AwaitTimer.StartDelayedSpinner();
                AwaitTimer.SetTimeout(delegate { LoadScriptWait(url); }, 13);
            }
            else
            {
                AwaitTimer.StopDelayedSpinner();
                Done();
            }
        }

        private void Log(string msg)
        {
            Script.Literal("if (window.console) console.log({0})", msg);
        }

// ReSharper disable UnusedParameter.Local
        private void Trace(string msg)
// ReSharper restore UnusedParameter.Local
        {
        }

        private void Error(string msg)
        {
            if ((bool) Script.Literal("window.console"))
                Script.Literal("window.console.error({0})", msg);
        }

        /*
        [AlternateSignature]
        public extern AwaitDelegate XCloserAw(Delegate fn, Await aw, object arg1);

        [AlternateSignature]
        public extern AwaitDelegate XCloserAw(Delegate fn, Await aw, object arg1, object arg2);

        [AlternateSignature]
        public extern AwaitDelegate XCloserAw(Delegate fn, Await aw, object arg1, object arg2, object arg3);

        public AwaitDelegate XCloserAw(Delegate fn, Await aw, object arg1, object arg2, object arg3, object arg4)
        {
            return delegate { Script.Literal("{0}({1},{2},{3},{4},{5})", fn, aw, arg1, arg2, arg3, arg4); };
        }
        */
    }
}